<div class="form">
    <div class="drive-panel">
        <div class="drive-header dx-treeview-item"><i class="dx-icon dx-icon-activefolder"></i><span>Drive C:</span></div>
        @(Html.DevExtreme().Sortable()
            .Filter(".dx-treeview-item")
            .Data("driveC")
            .Group("shared")
            .AllowReordering(true)
            .AllowDropInsideItem(true)
            .OnDragChange("onDragChange")
            .OnDragEnd("onDragEnd")
            .Content(
                Html.DevExtreme().TreeView()
                    .ID("treeviewDriveC")
                    .ExpandNodesRecursive(false)
                    .DataStructure(TreeViewDataStructure.Tree)
                    .KeyExpr("Id")
                    .ItemsExpr("Items")
                    .ExpandedExpr("IsExpanded")
                    .ItemTemplate(@<text>
                            <div>
                                <i class="dx-icon dx-icon-<%- IsDirectory ? 'activefolder' : 'file' %>"></i><span><%-Name%></span>
                            </div>
                        </text>)
                    .DataSource(d => d.Mvc().LoadAction("GetHierarchicalDataForDragAndDrop"))
                    .Width(250)
                    .Height(380).ToString()
            ))
    </div>
    <div class="drive-panel">
        <div class="drive-header dx-treeview-item"><i class="dx-icon dx-icon-activefolder"></i><span>Drive D:</span></div>
        @(Html.DevExtreme().Sortable()
            .Filter(".dx-treeview-item")
            .Data("driveD")
            .Group("shared")
            .AllowReordering(true)
            .AllowDropInsideItem(true)
            .OnDragChange("onDragChange")
            .OnDragEnd("onDragEnd")
            .Content(
                Html.DevExtreme().TreeView()
                    .ID("treeviewDriveD")
                    .ExpandNodesRecursive(false)
                    .DataStructure(TreeViewDataStructure.Tree)
                    .KeyExpr("Id")
                    .ItemsExpr("Items")
                    .ExpandedExpr("IsExpanded")
                    .ItemTemplate(@<text>
                            <div>
                                <i class="dx-icon dx-icon-<%- IsDirectory ? 'activefolder' : 'file' %>"></i><span><%-Name%></span>
                            </div>
                        </text>)
                    .Width(250)
                    .Height(380).ToString()
            )
        )
    </div>
</div>
<script>
    function onDragChange(e) {
        if(e.fromComponent === e.toComponent) {
            var $nodes = e.element.find(".dx-treeview-node");
            var isDragIntoChild = $nodes.eq(e.fromIndex).find($nodes.eq(e.toIndex)).length > 0;
            if(isDragIntoChild) {
                e.cancel = true;
            }
        }
    }

    function onDragEnd(e) {
        if(e.fromComponent === e.toComponent && e.fromIndex === e.toIndex) {
            return;
        }

        var fromTreeView = getTreeView(e.fromData);
        var toTreeView = getTreeView(e.toData);

        var fromNode = findNode(fromTreeView, e.fromIndex);
        var toNode = findNode(toTreeView, calculateToIndex(e));

        if(e.dropInsideItem && toNode !== null && !toNode.itemData.IsDirectory) {
            return;
        }

        var fromTopVisibleNode = getTopVisibleNode(fromTreeView);
        var toTopVisibleNode = getTopVisibleNode(toTreeView);

        var fromItems = fromTreeView.option('items');
        var toItems = toTreeView.option('items');
        moveNode(fromNode, toNode, fromItems, toItems, e.dropInsideItem);

        fromTreeView.option("items", fromItems);
        toTreeView.option("items", toItems);
        fromTreeView.scrollToItem(fromTopVisibleNode);
        toTreeView.scrollToItem(toTopVisibleNode);
    }

    function getTreeView(driveName) {
        return driveName === 'driveC'
            ? $('#treeviewDriveC').dxTreeView('instance')
            : $('#treeviewDriveD').dxTreeView('instance');
    }

    function calculateToIndex(e) {
        if(e.fromComponent != e.toComponent || e.dropInsideItem) {
            return e.toIndex;
        }

        return e.fromIndex >= e.toIndex
            ? e.toIndex
            : e.toIndex + 1;
    }

    function findNode(treeView, index) {
        var visibleItems = [];
        buildVisibleNodesArray(treeView.getNodes(), visibleItems);
        if(visibleItems.length <= index) {
            return null;
        }

        return visibleItems[index];
    }

    function buildVisibleNodesArray(nodes, arrayContainer) {
        for(var i = 0; i < nodes.length; i++) {
            arrayContainer.push(nodes[i]);
            if(nodes[i].expanded !== false && nodes[i].children){
                buildVisibleNodesArray(nodes[i].children, arrayContainer);
            }
        }
    }

    function moveNode(fromNode, toNode, fromItems, toItems, isDropInsideItem) {
        var fromNodeContainingArray = getNodeContainingArray(fromNode, fromItems);
        var fromIndex = findIndex(fromNodeContainingArray, fromNode.itemData.Id);
        fromNodeContainingArray.splice(fromIndex, 1);

        if(isDropInsideItem) {
            toNode.itemData.Items.splice(toNode.itemData.Items.length, 0, fromNode.itemData);
        } else {
            var toNodeContainingArray = getNodeContainingArray(toNode, toItems);
            var toIndex = toNode === null
                ? toItems.length
                : findIndex(toNodeContainingArray, toNode.itemData.Id);
            toNodeContainingArray.splice(toIndex, 0, fromNode.itemData);
        }
    }

    function getNodeContainingArray(node, rootArray) {
        return node === null || node.parent === null
            ? rootArray
            : node.parent.itemData.Items;
    }

    function findIndex(array, id) {
        var idsArray = array.map(function(elem) { return elem.Id; });
        return idsArray.indexOf(id);
    }

    function getTopVisibleNode(component) {
        var treeViewElement = component.element().get(0);
        var treeViewTopPosition = treeViewElement.getBoundingClientRect().top;
        var nodes = treeViewElement.querySelectorAll(".dx-treeview-node");
        for(var i = 0; i < nodes.length; i++) {
            var nodeTopPosition = nodes[i].getBoundingClientRect().top;
            if(nodeTopPosition >= treeViewTopPosition) {
                return nodes[i];
            }
        }

        return null;
    }
</script>
